package project2;
import java.io.Serializable;

public class PhysicsObject implements Serializable{
	private static final long serialVersionUID = 7740138002984958994L;
	//float inverseMass;
	float mass;
	Vector2f position;
	Vector2f velocity;
	Vector2f acceleration;
	Vector2f centerOfMass;
	float inverseMomentOfInertia;
	float orientation;
	float angularVelocity;
	float radius;
	SceneGraphNode renderable;
	
	
	public PhysicsObject() {
		mass = 1;
		position = new Vector2f();
		velocity = new Vector2f();
		acceleration = new Vector2f();
		centerOfMass = new Vector2f();
	}

	public void updateState(float timePeriod) {
		position.sumScale(velocity, timePeriod);
		position.sumScale(acceleration, timePeriod * timePeriod / 2);
		velocity.sumScale(acceleration, timePeriod);
		orientation += angularVelocity * timePeriod;
		clearCaches();
		
		if (renderable != null) {
			renderable.translateX = position.x;
			renderable.translateY = position.y;
		}
	}
	
	public void clearCaches() {
	}
	
	public void updateRenderable() {
		if (renderable != null) {
			renderable.translateX = position.x;
			renderable.translateY = position.y;
			renderable.rotation = (float)(180 * orientation / Math.PI);
		}
	}

	public CollisionInfo getCollision(PhysicsObject other) {
		if (mass == Float.POSITIVE_INFINITY && other.mass == Float.POSITIVE_INFINITY)
			return null;
		if (this instanceof HalfSpace && other instanceof PCircle)
			return getCollision((HalfSpace)this, (PCircle)other);
		if (this instanceof PCircle && other instanceof HalfSpace) {
			CollisionInfo cInfo = getCollision((HalfSpace)other, (PCircle)this);
			if (cInfo != null){
				cInfo.normal.scale(-1);
				Vector2f tmp = cInfo.positionA;
				cInfo.positionA = cInfo.positionB;
				cInfo.positionB = tmp;
			}
			return cInfo;
		}
		if (this instanceof HalfSpace && other instanceof GTriangle)
			return getCollision((HalfSpace)this, (GTriangle)other);
		if (this instanceof GTriangle && other instanceof HalfSpace) {
			CollisionInfo cInfo = getCollision((HalfSpace)other, (GTriangle)this);
			if (cInfo != null){
				cInfo.normal.scale(-1);
				Vector2f tmp = cInfo.positionA;
				cInfo.positionA = cInfo.positionB;
				cInfo.positionB = tmp;
			}
			return cInfo;
		}
		if (this instanceof HalfSpace && other instanceof PSquare)
			return getCollision((HalfSpace)this, (PSquare)other);		
		if (this instanceof PSquare && other instanceof HalfSpace){
			CollisionInfo cInfo = getCollision((HalfSpace)other, (PSquare)this);
			if (cInfo != null){
				cInfo.normal.scale(-1);
				Vector2f tmp = cInfo.positionA;
				cInfo.positionA = cInfo.positionB;
				cInfo.positionB = tmp;
			}
			return cInfo;
		}
		if (this instanceof HalfSpace && other instanceof PHouse)
			return getCollision((HalfSpace)this, (PHouse)other);		
		if (this instanceof PHouse && other instanceof HalfSpace){
			CollisionInfo cInfo = getCollision((HalfSpace)other, (PHouse)this);
			if (cInfo != null){
				cInfo.normal.scale(-1);
				Vector2f tmp = cInfo.positionA;
				cInfo.positionA = cInfo.positionB;
				cInfo.positionB = tmp;
			}
			return cInfo;
		}		
		///////other collisions//////////
		return getShapeCollision(this, other);
	}
	
	private static CollisionInfo getCollision(HalfSpace a, PCircle b) {
		float distance = a.normal.dot(b.position);
		distance -= a.intercept;
		if (distance > b.radius)
			return null;
		

		
	////If other nodes are connected to network, send object as appropriate/////
		if (GWorldMain.peer!= null && GWorldMain.peer.successor.id != GWorldMain.peer.myInfo.id
				&&GWorldMain.peer.predecessor!= null 
				&& GWorldMain.peer.predecessor.id != GWorldMain.peer.myInfo.id){
			if (!a.isBottomBoundary()){
				detachAndSendObject(a,b);
				return null;
			}
		}
		CollisionInfo cInfo = new CollisionInfo();
		cInfo.depth = b.radius - distance;
		cInfo.normal = new Vector2f(a.normal);
		cInfo.positionA = new Vector2f(b.position);
		cInfo.positionA.sumScale(cInfo.normal, -b.radius + cInfo.depth);
		cInfo.positionB = new Vector2f(b.position);
		cInfo.positionB.sumScale(cInfo.normal, -b.radius);
		return cInfo;
	}
	
	
	private static CollisionInfo getCollision(HalfSpace a, GTriangle b) {
		Vector2f[] vertices = b.getVertices();
		float[] distances = new float[vertices.length];
		
		for (int i = 0; i < vertices.length; i++)
			distances[i] = a.normal.dot(vertices[i]) - a.intercept;
		
		int minIndex = 0;
		for (int i = 1; i < distances.length; i++)
			if (distances[i] < distances[minIndex])
				minIndex = i;
		if (distances[minIndex] >= 0)
			return null;
		
	////If other nodes are connected to network///////
		if (GWorldMain.peer!= null && GWorldMain.peer.successor.id != GWorldMain.peer.myInfo.id
				&& GWorldMain.peer.predecessor!= null && 
				GWorldMain.peer.predecessor.id != GWorldMain.peer.myInfo.id){
			if (!a.isBottomBoundary()){
				detachAndSendObject(a,b);
				return null;
			}
		}
		
		CollisionInfo cInfo = new CollisionInfo();
		cInfo.depth = -distances[minIndex];
		cInfo.normal = new Vector2f(a.normal);
		cInfo.positionA = new Vector2f(vertices[minIndex]);
		cInfo.positionA.sumScale(cInfo.normal, cInfo.depth);
		cInfo.positionB = new Vector2f(vertices[minIndex]);
		return cInfo;
	}
	

	private static CollisionInfo getCollision(HalfSpace a, PSquare b) {
		Vector2f[] vertices = b.getVertices();
		float[] distances = new float[vertices.length];
		
		for (int i = 0; i < vertices.length; i++)
			distances[i] = a.normal.dot(vertices[i]) - a.intercept;
		
		int minIndex = 0;
		for (int i = 1; i < distances.length; i++)
			if (distances[i] < distances[minIndex])
				minIndex = i;
		if (distances[minIndex] >= 0)
			return null;	
		
		////If other nodes are connected to network
		if (GWorldMain.peer!= null && GWorldMain.peer.successor.id != GWorldMain.peer.myInfo.id
				&&GWorldMain.peer.predecessor!= null 
				&& GWorldMain.peer.predecessor.id != GWorldMain.peer.myInfo.id){
			if (!a.isBottomBoundary()){
				detachAndSendObject(a,b);
				return null;
			}
		}
			
		CollisionInfo cInfo = new CollisionInfo();
		cInfo.depth = -distances[minIndex];
		cInfo.normal = new Vector2f(a.normal);
		cInfo.positionA = new Vector2f(vertices[minIndex]);
		cInfo.positionA.sumScale(cInfo.normal, cInfo.depth);
		cInfo.positionB = new Vector2f(vertices[minIndex]);
		return cInfo;

	}
	
	
	private static CollisionInfo getCollision(HalfSpace a, PHouse b) {
		Vector2f[] vertices = b.getVertices();
		float[] distances = new float[vertices.length];
		
		for (int i = 0; i < vertices.length; i++)
			distances[i] = a.normal.dot(vertices[i]) - a.intercept;
		
		int minIndex = 0;
		for (int i = 1; i < distances.length; i++)
			if (distances[i] < distances[minIndex])
				minIndex = i;
		if (distances[minIndex] >= 0) //no collision
			return null;
		
		/////If other nodes are connected to network//////////////
		if (GWorldMain.peer!= null && GWorldMain.peer.successor.id != GWorldMain.peer.myInfo.id
				&&GWorldMain.peer.predecessor!= null 
				&& GWorldMain.peer.predecessor.id != GWorldMain.peer.myInfo.id){
				if (!a.isBottomBoundary()){
					detachAndSendObject(a,b);
					return null;
				}
			}
		
		
		CollisionInfo cInfo = new CollisionInfo();
		cInfo.depth = -distances[minIndex];
		cInfo.normal = new Vector2f(a.normal);
		cInfo.positionA = new Vector2f(vertices[minIndex]);
		cInfo.positionA.sumScale(cInfo.normal, cInfo.depth);
		cInfo.positionB = new Vector2f(vertices[minIndex]);
		return cInfo;
	}
	
	
	private static CollisionInfo getShapeCollision(PhysicsObject a, PhysicsObject b) {
		Vector2f tmp = new Vector2f(b.position);
		tmp.sumScale(a.position, -1);
		float distance = tmp.length() - a.radius - b.radius;
		if (distance > 0)
			return null;
		CollisionInfo cInfo = new CollisionInfo();
		cInfo.depth = -distance;
		tmp.normalize();
		cInfo.normal = tmp;
		cInfo.positionA = new Vector2f(a.position);
		cInfo.positionA.sumScale(cInfo.normal, a.radius);
		cInfo.positionB = new Vector2f(b.position);
		cInfo.positionB.sumScale(cInfo.normal, -b.radius);
		return cInfo;
	}
	
	
	public void resolveCollision(PhysicsObject other, CollisionInfo cInfo) {
		// Calculate the relative velocity between the calling object and
		// other object, as if the calling object were stationary and only
		// the other object were moving.
		Vector2f relativeVelocity = new Vector2f(other.velocity);
		relativeVelocity.sumScale(velocity, -1);
		// Calculate the component of the relative velocity that lays along
		// the collision normal.
		float compRelVelAlongNormal = relativeVelocity.dot(cInfo.normal);
		// If the objects are moving away from each other, do nothing and hope
		// the collision will resolve itself next frame.
		if (compRelVelAlongNormal >= 0)
			return;
		// Calculate the total desired change in velocity due to the collision
		// (technically an impulse).
		Vector2f totalChangeInVelocity = new Vector2f(cInfo.normal);
		totalChangeInVelocity.scale(2 * compRelVelAlongNormal);
		// Calculate the proportion of the total change in velocity that will
		// be applied to each object (mass proportionate).
		float proportionForA, proportionForB;
		// Temporary hack to handle infinite masses.
		if (mass == Float.POSITIVE_INFINITY) {
			proportionForA = 0;
			proportionForB = 1;
		} else if (other.mass == Float.POSITIVE_INFINITY) {
			proportionForA = 1;
			proportionForB = 0;
		} else {
			proportionForA = other.mass / (mass + other.mass);
			proportionForB = mass / (mass + other.mass);
		}
		// Apply the correct velocity change to each object.
		velocity.sumScale(totalChangeInVelocity, proportionForA);
		other.velocity.sumScale(totalChangeInVelocity, -proportionForB);
	}
	
	//returns node# to send to when this object leaves the world from the top.
	private static long calculatePeer(PhysicsObject shape){
		long peerId;
		double angle, x, y,length; //angle leaving world
		//determine angle at which it is leaving the world
		if (shape.velocity.x < 0){
			angle = -Math.atan((double)shape.velocity.y/shape.velocity.x);
		}else{
			angle = 2*Math.PI -Math.atan((double)shape.velocity.y/shape.velocity.x);
		}
		//////   x^2 + y^2 = 1 and y = angle*x +1 factor to the following: /////
		x = (-2*angle) / (1+Math.pow(angle, 2));
		y = angle*x + 1;
		length= Math.PI / 2 + Math.atan(y/x); //might need to be tweaked?
		//now find node#
		if (shape.velocity.x < 0)
			peerId = (long)(((length / 2 * Math.PI )* 1024)+ GWorldMain.peer.myInfo.id)%1024;
		else
			peerId = (long)(GWorldMain.peer.myInfo.id -((2* Math.PI) - ((length / 2 * Math.PI) * 1024)))%1024;
		//System.out.println("angle = " + angle + "send to peer = " + peerId);
		return peerId;
	}
	
	private static void detachAndSendObject(PhysicsObject halfSpace, PhysicsObject shape){
		long peerIDforTransmit = 0;
		if (((HalfSpace)halfSpace).isTopBoundary()){
			peerIDforTransmit = calculatePeer(shape);
			shape.velocity.x = - shape.velocity.x;
			shape.velocity.y = -shape.velocity.y;
			shape.position.y = shape.position.y-.5f;
			shape.acceleration.x = -shape.acceleration.x;
			shape.acceleration.y = -shape.acceleration.y;
		} else if (((HalfSpace)halfSpace).isLeftBoundary()){
			peerIDforTransmit = GWorldMain.peer.successor.id;
			//adjust position to reflect position when it gets to peer
			shape.position.x = - shape.position.x -shape.renderable.scale-.3f;
		} else if (((HalfSpace)halfSpace).isRighttBoundary()){
			peerIDforTransmit = GWorldMain.peer.predecessor.id;
			shape.position.x = -shape.position.x + .3f;
		}
		GWorldMain.peer.sendObject("here's a shape", peerIDforTransmit,shape);
		GWorldMain.detachObject(shape);
	}
}
